
#include "stdafx.h"

#include <cstdio>
#include <string>

#include <vcclr.h>

#include "ImageCodecPlugin.h"

using namespace std;
using namespace Derm;

using namespace System::Runtime::InteropServices;

/*===========================================================================*/
/* libTIFF Interoperability - Stream                                         */
/*===========================================================================*/

typedef struct _TiffStream {
	gcroot<Stream^> Handle;
} TiffStream;

static tsize_t LibTiffReadProc(thandle_t handle, tdata_t buf, tsize_t size)
{
	TiffStream *stream = (TiffStream*)handle;
	cli::array<unsigned char> ^buffer = gcnew cli::array<unsigned char>(size + 2);
	int bytes;

	// Read from file
	bytes = stream->Handle->Read(buffer, 0, size);
	// Copy to buffer
	Marshal::Copy(buffer, 0, (IntPtr)buf, bytes);

	return (bytes);
}

static tsize_t LibTiffWriteProc(thandle_t handle, tdata_t buf, tsize_t size)
{
	TiffStream *stream = (TiffStream*)handle;
	cli::array<unsigned char> ^buffer = gcnew cli::array<unsigned char>(size + 2);

	// Copy to buffer
	Marshal::Copy((IntPtr)buf, buffer, 0, size);
	// Read from file
	stream->Handle->Write(buffer, 0, size);

	return (size);
}

static toff_t LibTiffSeekProc(thandle_t handle, toff_t off, int whence)
{
	TiffStream *stream = (TiffStream*)handle;

	switch (whence) {
		case SEEK_SET:
			stream->Handle->Seek(off, SeekOrigin::Begin);
			break;
		case SEEK_CUR:
			stream->Handle->Seek(off, SeekOrigin::Current);
			break;
		case SEEK_END:
			stream->Handle->Seek(off, SeekOrigin::End);
			break;
	}

	return (stream->Handle->Position);
}

static toff_t LibTiffSizeProc(thandle_t handle)
{
	TiffStream *stream = (TiffStream*)handle;

	return (stream->Handle->Length);
}

static int LibTiffCloseProc(thandle_t fd)
{
	return (0);
}

static int LibTiffMapProc(thandle_t fd, tdata_t* pbase, toff_t* psize)
{
	return (0);
}

static void LibTiffUnmapProc(thandle_t fd, tdata_t base, toff_t size)
{
	
}

/*===========================================================================*/
/* libTIFF Interoperability - Memory                                         */
/*===========================================================================*/

tdata_t _TIFFmalloc(tsize_t s) { return malloc(s); }

void _TIFFfree(tdata_t p) { free(p); }

tdata_t _TIFFrealloc(tdata_t p, tsize_t s) { return realloc(p, s); }

void _TIFFmemset(tdata_t p, int v, tsize_t c) { memset(p, v, (size_t) c); }

void _TIFFmemcpy(tdata_t d, const tdata_t s, tsize_t c) { memcpy(d, s, (size_t) c); }

int _TIFFmemcmp(const tdata_t p1, const tdata_t p2, tsize_t c) { return (memcmp(p1, p2, (size_t) c)); }

/*===========================================================================*/
/* libTIFF Interoperability - Verbosity                                      */
/*===========================================================================*/

String ^GetPhotometric(uint16 photometric)
{
	switch (photometric) {
		case PHOTOMETRIC_RGB:
			return ("RGB");
		case PHOTOMETRIC_YCBCR:
			return ("YCbCr");
		case PHOTOMETRIC_CIELAB:
			return ("CIE LAB");
		case PHOTOMETRIC_ICCLAB:
			return ("ICC LAB");
		case PHOTOMETRIC_ITULAB:
			return ("ITU LAB");
		case PHOTOMETRIC_LOGL:
			return ("LOGL");
		case PHOTOMETRIC_LOGLUV:
			return ("LOGLUV");
		case PHOTOMETRIC_SEPARATED:
			return ("SEPARATED");
		case PHOTOMETRIC_MINISWHITE:
			return ("MINISWHITE");
		case PHOTOMETRIC_MINISBLACK:
			return ("MINISBLACK");
		case PHOTOMETRIC_PALETTE:
			return ("PALETTE");
		case PHOTOMETRIC_MASK:
			return ("MASK");
		default:
			return ("UNKNOWN");
	}
}

/*===========================================================================*/
/* libTIFF Interoperability - Error Handling                                 */
/*===========================================================================*/

static void msdosWarningHandler(const char* module, const char* fmt, va_list ap)
{
	//fprintf_s(stdout, fmt, ap);
}

TIFFErrorHandler _TIFFwarningHandler = msdosWarningHandler;

static void msdosErrorHandler(const char* module, const char* fmt, va_list ap)
{
	//fprintf_s(stdout, fmt, ap);
}

TIFFErrorHandler _TIFFerrorHandler = msdosErrorHandler;

/*===========================================================================*/
/* TiffImageCodecPlugin                                                      */
/*===========================================================================*/

Pixel::Type TiffImageCodecPlugin::GetPixelTypeFromTiff(TIFF *tif, ImageCodec::CodecCriteria ^criteria)
{
	uint16 photometric = 0;
	uint16 bitPerSample = 0;
	uint16 samplesPerPixel = 0;
	uint16 sampleFormat = 0;
	uint16 bitPerPixel;
	uint16 extraAlphaSampleIndex = 0;

	bool useIntegerPixel = (criteria->IsDefined(ImageCodec::IntegerFormatSupport) == true) && ((Boolean)criteria[ImageCodec::IntegerFormatSupport] == true);

	// Determine pixel format (default to UINT)
	if ((TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &sampleFormat) == 0) || (sampleFormat == SAMPLEFORMAT_VOID))
		sampleFormat = SAMPLEFORMAT_UINT;

	// Determine pixel bit count
	if (TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric) == 0)
		throw gcnew InvalidOperationException("unknown photometric");
	if (TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bitPerSample) == 0)
		throw gcnew InvalidOperationException("unknown bits per sample");
	// Other extra channels are not considered
	samplesPerPixel = GetRealSamplesPerPixel(tif);
	// Derive the meaningfull bits in pixel
	bitPerPixel = bitPerSample * samplesPerPixel;

	// Determine sRGB color space from EXIF tags
	bool sRGB = false;
	uint16 sRGBCode;

	if (TIFFGetField(tif, EXIFTAG_COLORSPACE, &sRGBCode) != 0) {
		switch (sRGBCode) {
			case 0x0001:
				sRGB = true;
				break;
		}
	}

	switch (photometric) {
		case PHOTOMETRIC_RGB:
			switch (samplesPerPixel) {
				case 3:
					switch (sampleFormat) {
						case SAMPLEFORMAT_UINT:
							switch (bitPerPixel) {
								case 3:
								case 6:
								case 12:
									return (Pixel::Type::RGB24);
								case 8:
									return (Pixel::Type::RGB8);
								case 15:
									return (Pixel::Type::RGB15);
								case 16:
									return (Pixel::Type::RGB16);
								case 24:
									return ((sRGB == TRUE) ? Pixel::Type::SRGB24 : Pixel::Type::RGB24);
								case 48:
									return (Pixel::Type::RGB48);
								case 96:
									if (useIntegerPixel == true)
										return (Pixel::Type::RGB96);
									else
										return (Pixel::Type::RGBF);
								default:
									throw gcnew InvalidOperationException("RGB pixel format (INT) with " + bitPerPixel + " bits is not supported");
							}
							break;
						case SAMPLEFORMAT_INT:
							throw gcnew InvalidOperationException("RGB pixel format (INT) with " + bitPerPixel + " bits is not supported");
							break;
						case SAMPLEFORMAT_IEEEFP:
							switch (bitPerPixel) {
								case 48:
									return (Pixel::Type::RGBHF);
								case 96:
									return (Pixel::Type::RGBF);
								case 192:
									return (Pixel::Type::RGBD);
								default:
									throw gcnew InvalidOperationException("RGB pixel format (FLOAT) with " + bitPerPixel + " bit is not supported");
							}
						case SAMPLEFORMAT_COMPLEXIEEEFP:
							throw gcnew InvalidOperationException("RGB pixel format (FLOAT COMPLEX) is not supported");
							break;
						case SAMPLEFORMAT_COMPLEXINT:
							throw gcnew InvalidOperationException("RGB pixel format (INT COMPLEX) is not supported");
							break;
						default:
							throw gcnew InvalidOperationException("RGB pixel format (UNKNOWN) is not supported");
							break;
					}
					break;
				case 4:
					switch (sampleFormat) {
						case SAMPLEFORMAT_UINT:
							switch (bitPerPixel) {
								case 4:
								case 8:
								case 16:
									return (Pixel::Type::RGBA32);
								case 32:
									return (Pixel::Type::RGBA32);
								case 64:
									return (Pixel::Type::RGBA64);
								case 128:
									if (useIntegerPixel == true)
										return (Pixel::Type::RGBA128);
									else
										return (Pixel::Type::RGBAF);
								default:
									throw gcnew InvalidOperationException("RGBA pixel format (UINT) with " + bitPerPixel + " bits is not supported");
							}
							break;
						case SAMPLEFORMAT_INT:
							throw gcnew InvalidOperationException("RGBA pixel format (INT) with " + bitPerPixel + " bits is not supported");
							break;
						case SAMPLEFORMAT_IEEEFP:
							switch (bitPerPixel) {
								case 64:
									return (Pixel::Type::RGBAHF);
								case 128:
									return (Pixel::Type::RGBAF);
								default:
									throw gcnew InvalidOperationException("RGBA pixel format (FLOAT) with " + bitPerPixel + " bits is not supported");
							}
						case SAMPLEFORMAT_COMPLEXIEEEFP:
							throw gcnew InvalidOperationException("RGBA pixel format (FLOAT COMPLEX) is not supported");
							break;
						case SAMPLEFORMAT_COMPLEXINT:
							throw gcnew InvalidOperationException("RGBA pixel format (INT COMPLEX) is not supported");
							break;
					}
					break;
				default:
					throw gcnew InvalidOperationException("RGB(A) pixel format with " + samplesPerPixel + " samples is not supported");
			}
			break;
		case PHOTOMETRIC_YCBCR:
		case PHOTOMETRIC_CIELAB:
		case PHOTOMETRIC_ICCLAB:
		case PHOTOMETRIC_ITULAB:
			return (Pixel::Type::RGB24);
		case PHOTOMETRIC_LOGL:
			switch (samplesPerPixel) {
				case 1:
					switch (sampleFormat) {
						case SAMPLEFORMAT_INT:
							switch (bitPerPixel) {
								case 16:
									return (Pixel::Type::GRAYF);
								default:
									throw gcnew InvalidOperationException("LOGL pixel format (INT) with " + bitPerPixel + " bit is not supported");
							}
							break;
						default:
							throw gcnew InvalidOperationException("LOGL pixel format (non INT) is not supported");
					}
					break;
				default:
					throw gcnew InvalidOperationException("LOGL pixel format with " + samplesPerPixel + " samples is not supported");
			}
			break;
		case PHOTOMETRIC_LOGLUV:
			switch (samplesPerPixel) {
				case 3:
					switch (sampleFormat) {
						case SAMPLEFORMAT_INT:
							switch (bitPerPixel) {
								case 48:
								case 96:
									return (Pixel::Type::RGBF);
								default:
									throw gcnew InvalidOperationException("LOGLUV pixel format (INT) with " + bitPerPixel + " bit is not supported");
							}
							break;
						default:
							throw gcnew InvalidOperationException("LOGLUV pixel format (non INT) is not supported");
					}
					break;
				default:
					throw gcnew InvalidOperationException("LOGLUV pixel format with " + samplesPerPixel + " samples is not supported");
			}
			break;
		case PHOTOMETRIC_SEPARATED:
			switch (samplesPerPixel) {
				case 4:
					switch (sampleFormat) {
						case SAMPLEFORMAT_UINT:
							switch (bitPerPixel) {
								case 32:
									return (Pixel::Type::CMYK32);
								case 64:
									return (Pixel::Type::CMYK64);
								default:
									throw gcnew InvalidOperationException("CMYK pixel format (UINT) with " + bitPerPixel + " bits is not supported");
							}
						case SAMPLEFORMAT_INT:
							throw gcnew InvalidOperationException("CMYK pixel format (INT) is not supported");
						case SAMPLEFORMAT_IEEEFP:
							throw gcnew InvalidOperationException("CMYK pixel format (FLOAT) is not supported");
						case SAMPLEFORMAT_COMPLEXIEEEFP:
							throw gcnew InvalidOperationException("CMYK pixel format (FLOAT COMPLEX) is not supported");
						case SAMPLEFORMAT_COMPLEXINT:
							throw gcnew InvalidOperationException("CMYK pixel format (INT COMPLEX) is not supported");
						default:
							throw gcnew InvalidOperationException("CMYK pixel format with UNKNOWN sample format is not supported");
					}
					break;
				case 5:
					switch (sampleFormat) {
						case SAMPLEFORMAT_UINT:
							switch (bitPerPixel) {
								case 40:
									return (Pixel::Type::CMYKA40);
								default:
									throw gcnew InvalidOperationException("CMYKA pixel format (UINT) with " + bitPerPixel + " bits is not supported");
							}
						case SAMPLEFORMAT_INT:
							throw gcnew InvalidOperationException("CMYKA pixel format (INT) is not supported");
						case SAMPLEFORMAT_IEEEFP:
							throw gcnew InvalidOperationException("CMYKA pixel format (FLOAT) is not supported");
						case SAMPLEFORMAT_COMPLEXIEEEFP:
							throw gcnew InvalidOperationException("CMYKA pixel format (FLOAT COMPLEX) is not supported");
						case SAMPLEFORMAT_COMPLEXINT:
							throw gcnew InvalidOperationException("CMYKA pixel format (INT COMPLEX) is not supported");
						default:
							throw gcnew InvalidOperationException("CMYKA pixel format with UNKNOWN sample format is not supported");
					}
					break;
				default:
					throw gcnew InvalidOperationException("CMYK pixel format with " + samplesPerPixel + " samples is not supported");
			}
		case PHOTOMETRIC_MINISWHITE:
		case PHOTOMETRIC_MINISBLACK:
			switch (samplesPerPixel) {
				case 1:
					switch (sampleFormat) {
						case SAMPLEFORMAT_UINT:
							switch (bitPerPixel) {
								case 1:
								case 2:
								case 4:
								case 8:
									return (Pixel::Type::GRAY8);
								case 16:
									return (Pixel::Type::GRAY16);
								default:
									throw gcnew InvalidOperationException("MINIS* pixel format (UINT) with " + bitPerPixel + " bits is not supported");
							}
							break;
						case SAMPLEFORMAT_INT:
							throw gcnew InvalidOperationException("MINIS* pixel format (INT) is not supported");
						case SAMPLEFORMAT_IEEEFP:
							throw gcnew InvalidOperationException("MINIS* pixel format (FLOAT) is not supported");
						case SAMPLEFORMAT_COMPLEXIEEEFP:
							throw gcnew InvalidOperationException("MINIS* pixel format (FLOAT COMPLEX) is not supported");
						case SAMPLEFORMAT_COMPLEXINT:
							throw gcnew InvalidOperationException("MINIS* pixel format (INT COMPLEX) is not supported");
						default:
							throw gcnew InvalidOperationException("MINIS* pixel format with UNKNOWN sample format is not supported");
					}
					break;
				default:
					throw gcnew InvalidOperationException("MINIS* pixel format with " + samplesPerPixel + " samples is not supported");
			}
			break;
		case PHOTOMETRIC_PALETTE:
			return ((sRGB == TRUE) ? Pixel::Type::SRGB24 : Pixel::Type::RGB24);
		default:
			throw gcnew InvalidOperationException("image photometric " + GetPhotometric(photometric) + " is not supported");
	}
}

void TiffImageCodecPlugin::LoadGenericRgbaTiff(TIFF *tif, Image ^image)
{
	TIFFErrorHandler errHandler = NULL;
	uint32 *raster = NULL;
	uint32 height = 0; 
	uint32 width = 0; 
	uint16 photometric = 0;
	uint16 samplesPerPixel = 0, bitPerSample = 0;

	if (TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric) == 0)
		throw gcnew InvalidOperationException("unknown photometric");
	if (TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width) == 0)
		throw gcnew InvalidOperationException("unknown width");
	if (TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height) == 0)
		throw gcnew InvalidOperationException("unknown height");
	if (TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bitPerSample) == 0)
		throw gcnew InvalidOperationException("unknown bits per sample");

	// Determine the samples per pixel
	samplesPerPixel = GetRealSamplesPerPixel(tif);
	// Palette TIFFs have 1 component per pixel, but used for redirect to a RGB tuple
	if (photometric == PHOTOMETRIC_PALETTE)
		samplesPerPixel = 3 + ((HasAssociatedAlpha(tif) == true) ? 1 : 0);

	if ((samplesPerPixel != 1) && (samplesPerPixel != 3) && (samplesPerPixel != 4))
		throw gcnew InvalidOperationException(GetPhotometric(photometric) + " pixel format with " + samplesPerPixel + " components per pixel is not supported in RGBA mode");
	if ((bitPerSample != 1) && (bitPerSample != 2) && (bitPerSample != 4) && (bitPerSample != 8) && (bitPerSample != 16))
		throw gcnew InvalidOperationException(GetPhotometric(photometric) + " pixel format with " + bitPerSample + " bits per sample is not supported in RGBA mode");

	__try {
		// Set temporary error handler
		errHandler = TIFFSetErrorHandler(msdosErrorHandler);

		if ((raster = (uint32*)_TIFFmalloc(image->Width * image->Height * sizeof(uint32))) == NULL)
			throw gcnew OutOfMemoryException("");

		if (TIFFReadRGBAImage(tif, width, height, raster, 1) == 0)
			throw gcnew InvalidOperationException("TIFF format not supported in RGBA mode");

		GCHandle ^hImageData = GCHandle::Alloc(image->GetPixelData(), GCHandleType::Pinned);

		__try {
			unsigned char *imageDataPtr = reinterpret_cast<unsigned char*>(hImageData->AddrOfPinnedObject().ToPointer());

			uint32 *row = &raster[0];

			if        (samplesPerPixel == 4) {
				for (int y = 0; y < height; y++) {
					unsigned char *line = imageDataPtr + y * image->Stride;

					for (uint32 x = 0; x < width; x++) {
						line[2]	= (unsigned char)TIFFGetB(row[x]);
						line[1] = (unsigned char)TIFFGetG(row[x]);
						line[0]	= (unsigned char)TIFFGetR(row[x]);
						line[3] = (unsigned char)TIFFGetA(row[x]);

						line += 4;
					}
					row += width;
				}
			} else if (samplesPerPixel == 3) {
				for (int y = 0; y < height; y++) {
					unsigned char *line = imageDataPtr + y * image->Stride;

					for (uint32 x = 0; x < width; x++) {
						line[2]	= (unsigned char)TIFFGetB(row[x]);
						line[1] = (unsigned char)TIFFGetG(row[x]);
						line[0]	= (unsigned char)TIFFGetR(row[x]);

						line += 3;
					}
					row += width;
				}
			} else if (samplesPerPixel == 1) {
				for (int y = 0; y < height; y++) {
					unsigned char *line = imageDataPtr + y * image->Stride;

					for (uint32 x = 0; x < width; x++) {
						line[0]	= (unsigned char)TIFFGetR(row[x]);

						line += 1;
					}
					row += width;
				}
			}
		} __finally {
			hImageData->Free();
		}
	} __finally {
		if (raster != NULL)
			_TIFFfree(raster);
		TIFFSetErrorHandler(errHandler);
	}
}

void TiffImageCodecPlugin::LoadFloatingPrecisionTiff(TIFF *tif, Image ^image)
{
	// Load image as tiled/stripped
	LoadGenericTiff(tif, image);
}

void TiffImageCodecPlugin::LoadLogarithmicTiff(TIFF *tif, Image ^image)
{
	Image ^rawimage = nullptr;
	uint16 photometric = 0;
	uint16 bitPerSample = 0;
	uint16 samplesPerPixel = 0;
	uint16 sampleFormat = 0;
	uint16 bitPerPixel;

	// Determine pixel format (default to SAMPLEFORMAT_UINT)
	if ((TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &sampleFormat) == 0) || (sampleFormat == SAMPLEFORMAT_VOID))
		sampleFormat = SAMPLEFORMAT_UINT;

	// Determine pixel bit count
	TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric);
	TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel);
	TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bitPerSample);
	bitPerPixel = bitPerSample * samplesPerPixel;

	switch (samplesPerPixel) {
		case 1:
			rawimage = image;
			break;
		case 3:
			rawimage = Image::CreateImage(image->Width, image->Height, Pixel::Type::RGBF);
			break;
		default:
			throw gcnew InvalidOperationException(GetPhotometric(photometric) + " pixel format with " + samplesPerPixel + " samples is not supported");
	}

	// Output image using single-precision floating-point samples
	TIFFSetField(tif, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT);

	// Load XYZ image (assumes CCIR-709 primaries)
	LoadGenericTiff(tif, rawimage);

	// Convert LOG Luv XYZ (D65) -> RGB
	GCHandle ^hXyzImageData = GCHandle::Alloc(rawimage->GetPixelData(), GCHandleType::Pinned);
	GCHandle ^hRgbImageData = GCHandle::Alloc(image->GetPixelData(), GCHandleType::Pinned);

	__try {
		float *imageRgbDataPtr = reinterpret_cast<float*>(hRgbImageData->AddrOfPinnedObject().ToPointer());
		double stonits = 1.0;

#if 0
		if (TIFFGetField(tif, TIFFTAG_STONITS, &stonits) == 0)
			stonits = 1.0;
#endif

		switch (samplesPerPixel) {
			case 3:
				{
					float *imageXyzDataPtr = reinterpret_cast<float*>(hXyzImageData->AddrOfPinnedObject().ToPointer());
					float *imageXyzDataPtrEnd = imageXyzDataPtr + image->Width * image->Height * samplesPerPixel;

					for (; imageXyzDataPtr < imageXyzDataPtrEnd; imageXyzDataPtr += samplesPerPixel, imageRgbDataPtr += samplesPerPixel)
						ConvertLUV2RGB(imageXyzDataPtr, imageRgbDataPtr, stonits);
				}
				break;
		}
	} __finally {
		hXyzImageData->Free();
		hRgbImageData->Free();
	}
}

#ifdef  NTSC
#define  CIE_x_r		0.670		/* standard NTSC primaries */
#define  CIE_y_r		0.330
#define  CIE_x_g		0.210
#define  CIE_y_g		0.710
#define  CIE_x_b		0.140
#define  CIE_y_b		0.080
#define  CIE_x_w		0.3333		/* use true white */
#define  CIE_y_w		0.3333
#else
#define  CIE_x_r		0.640		/* nominal CRT primaries */
#define  CIE_y_r		0.330
#define  CIE_x_g		0.290
#define  CIE_y_g		0.600
#define  CIE_x_b		0.150
#define  CIE_y_b		0.060
#define  CIE_x_w		0.3333		/* use true white */
#define  CIE_y_w		0.3333
#endif

#define CIE_D		(CIE_x_r*(CIE_y_g - CIE_y_b) + CIE_x_g*(CIE_y_b - CIE_y_r) + CIE_x_b*(CIE_y_r - CIE_y_g))
#define CIE_C_rD	((1.0/CIE_y_w) * (CIE_x_w*(CIE_y_g - CIE_y_b) - CIE_y_w*(CIE_x_g - CIE_x_b) + CIE_x_g*CIE_y_b - CIE_x_b*CIE_y_g))
#define CIE_C_gD	((1.0/CIE_y_w) * (CIE_x_w*(CIE_y_b - CIE_y_r) - CIE_y_w*(CIE_x_b - CIE_x_r) - CIE_x_r*CIE_y_b + CIE_x_b*CIE_y_r))
#define CIE_C_bD	((1.0/CIE_y_w) * (CIE_x_w*(CIE_y_r - CIE_y_g) - CIE_y_w*(CIE_x_r - CIE_x_g) + CIE_x_r*CIE_y_g - CIE_x_g*CIE_y_r))

#define CIE_XYZ_RGB_11		((CIE_y_g - CIE_y_b - CIE_x_b*CIE_y_g + CIE_y_b*CIE_x_g)/CIE_C_rD)
#define CIE_XYZ_RGB_12		((CIE_x_b - CIE_x_g - CIE_x_b*CIE_y_g + CIE_x_g*CIE_y_b)/CIE_C_rD)
#define CIE_XYZ_RGB_13		((                    CIE_x_g*CIE_y_b - CIE_x_b*CIE_y_g)/CIE_C_rD)

#define CIE_XYZ_RGB_21		((CIE_y_b - CIE_y_r - CIE_y_b*CIE_x_r + CIE_y_r*CIE_x_b)/CIE_C_gD)
#define CIE_XYZ_RGB_22		((CIE_x_r - CIE_x_b - CIE_x_r*CIE_y_b + CIE_x_b*CIE_y_r)/CIE_C_gD)
#define CIE_XYZ_RGB_23		((                    CIE_x_b*CIE_y_r - CIE_x_r*CIE_y_b)/CIE_C_gD)

#define CIE_XYZ_RGB_31		((CIE_y_r - CIE_y_g - CIE_y_r*CIE_x_g + CIE_y_g*CIE_x_r)/CIE_C_bD)
#define CIE_XYZ_RGB_32		((CIE_x_g - CIE_x_r - CIE_x_g*CIE_y_r + CIE_x_r*CIE_y_g)/CIE_C_bD)
#define CIE_XYZ_RGB_33		((                    CIE_x_r*CIE_y_g - CIE_x_g*CIE_y_r)/CIE_C_bD)

void TiffImageCodecPlugin::ConvertLUV2RGB(float *src, float *dst, double stonits)
{
	double x = (double)src[0];
	double y = (double)src[1];
	double z = (double)src[2];

	dst[0]	= max<float>(0.0f, (float)(CIE_XYZ_RGB_11*x + CIE_XYZ_RGB_12*y + CIE_XYZ_RGB_13*z) * (float)stonits);
	dst[1]	= max<float>(0.0f, (float)(CIE_XYZ_RGB_21*x + CIE_XYZ_RGB_22*y + CIE_XYZ_RGB_23*z) * (float)stonits);
	dst[2]	= max<float>(0.0f, (float)(CIE_XYZ_RGB_31*x + CIE_XYZ_RGB_23*y + CIE_XYZ_RGB_33*z) * (float)stonits);
}

void TiffImageCodecPlugin::LoadGenericTiff(TIFF *tif, Image ^image)
{
	bool tiled = (TIFFIsTiled(tif) == 0) ? false : true;

	// Load stripped/tiles TIFF
	if (tiled == false)
		LoadGenericStripTiff(tif, image);
	else
		LoadGenericTileTiff(tif, image);

	// Compensate pre-multiplied alpha
	PostDivideAlpha(tif, image);
	// Orientate as needed (TIFF stored as specified in file)
	OrientateTiff(tif, image);
}

void TiffImageCodecPlugin::LoadGenericStripTiff(TIFF *tif, Image ^image)
{
	uint32 height = 0;
	uint32 width = 0;
	uint32 rowsperstrip = 0;
	uint16 bitPerSample = 0;
	uint16 samplesPerPixel;
	uint16 planarConfig;
	int extraChannels = 0;

	if (TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width) == 0)
		throw gcnew InvalidOperationException("unknown width");
	if (TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height) == 0)
		throw gcnew InvalidOperationException("unknown height");
	if (TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bitPerSample) == 0)
		throw gcnew InvalidOperationException("unknown bits per sample");
	if (TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip) == 0)
		throw gcnew InvalidOperationException("unknown rows per strip");
	if (TIFFGetFieldDefaulted(tif, TIFFTAG_PLANARCONFIG, &planarConfig) == 0)
		throw gcnew InvalidOperationException("unknown planar configuration");

	// Determine the samples per pixel
	samplesPerPixel = GetRealSamplesPerPixel(tif, extraChannels);
	if (extraChannels > 0)
		throw gcnew InvalidOperationException("stripped TIFF with extra channels is not supported");
	// Derive the bits per pixel
	uint16 bitPerPixel = bitPerSample * samplesPerPixel;
	uint16 bytePerComponent = bitPerPixel / 8 * samplesPerPixel;

	GCHandle ^hImageData = GCHandle::Alloc(image->GetPixelData(), GCHandleType::Pinned);

	__try {
		unsigned char *imageDataPtr = reinterpret_cast<unsigned char*>(hImageData->AddrOfPinnedObject().ToPointer());

		if (planarConfig == PLANARCONFIG_CONTIG) {
			const tsize_t srcStride = TIFFStripSize(tif);

			unsigned char *imageDataPtrCursor = imageDataPtr;

			for (uint32 y = 0; y < height; y += rowsperstrip) {
				const int32 strips = (y + rowsperstrip > height ? height - y : rowsperstrip);

				if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, y, 0), imageDataPtrCursor, srcStride) < 0) {
					// Ignore errors
				}

				imageDataPtrCursor += srcStride;
			}
		} else if (planarConfig == PLANARCONFIG_SEPARATE) {
			const tsize_t srcStride = TIFFStripSize(tif);

			unsigned char *imageDataPtrCursor;

			uint8 *raster;

			if ((raster = (uint8*)_TIFFmalloc(srcStride)) == NULL)
				throw gcnew OutOfMemoryException("");
			__try {

				// Iterate over strip blocks
				for(uint16 sample = 0; sample < samplesPerPixel; sample++) {
					const uint32 sampleSize = image->PixelSize / Pixel::GetComponentsCount(image->PixelFormat);

					imageDataPtrCursor = imageDataPtr + sample * sampleSize;

					for (uint32 y = 0; y < height; y += rowsperstrip, imageDataPtrCursor += image->Stride * rowsperstrip) {
				 		const uint32 strips = (y + rowsperstrip > height ? height - y : rowsperstrip);

						if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, y, sample), raster, srcStride) < 0) {
							// Ignore errors
						}

						// Interleave samples
						for (uint32 line = 0; line < strips; line++) {
							for (uint32 x = 0; x < width; x++)
								memcpy(imageDataPtrCursor + (x * image->PixelSize), raster + (x * sampleSize), sampleSize);
						}
					}
				}
			} __finally {
				if (raster != NULL)
					_TIFFfree(raster);
			}
		}
	} __finally {
		hImageData->Free();
	}
}

void TiffImageCodecPlugin::LoadGenericTileTiff(TIFF *tif, Image ^image)
{
	uint32 height = 0;
	uint32 width = 0;
	uint32 tileWidth, tileHeight;
	uint16 planarConfig;
	int extraChannels = 0;

	if (TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width) == 0)
		throw gcnew InvalidOperationException("unknown width");
	if (TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height) == 0)
		throw gcnew InvalidOperationException("unknown height");
	if (TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tileWidth) == 0)
		throw gcnew InvalidOperationException("unknown tile width");
	if (TIFFGetField(tif, TIFFTAG_TILELENGTH, &tileHeight) == 0)
		throw gcnew InvalidOperationException("unknown tile height");
	if (TIFFGetFieldDefaulted(tif, TIFFTAG_PLANARCONFIG, &planarConfig) == 0)
		throw gcnew InvalidOperationException("unknown planar configuration");

	// Determine the samples per pixel
	uint16 samplesPerPixel = GetRealSamplesPerPixel(tif, extraChannels);
	if (extraChannels > 0)
		throw gcnew InvalidOperationException("stripped TIFF with extra channels is not supported");

	const tsize_t srcStride = TIFFScanlineSize(tif);
	const tsize_t dstStride = image->Stride;

	GCHandle ^hImageData = GCHandle::Alloc(image->GetPixelData(), GCHandleType::Pinned);

	__try {
		unsigned char *imageDataPtr = reinterpret_cast<unsigned char*>(hImageData->AddrOfPinnedObject().ToPointer());
		unsigned char *imageDataPtrCursor = imageDataPtr;

		if (planarConfig == PLANARCONFIG_CONTIG) {
			uint8 *raster = NULL;
			tsize_t tileSize = TIFFTileSize(tif);
			tsize_t tileRowSize = TIFFTileRowSize(tif);

			if (srcStride != dstStride)
				throw gcnew InvalidOperationException("stride mismatch");

			__try {
				if ((raster = (uint8*)_TIFFmalloc(tileSize)) == NULL)
					throw gcnew OutOfMemoryException("");
				
				for (uint32 y = 0; y < height; y += tileHeight) {
					int32 strips = (y + tileHeight > height ? height - y : tileHeight);

					for (uint32 x = 0; x < width; x += tileWidth) {
						int32 pixelbytes = (x + tileWidth > width ? width - x : tileWidth) * (tileRowSize / tileWidth);
						unsigned char *imageDataPtrTileCursor = imageDataPtr + (y * dstStride) + (x * image->PixelSize);

						if (TIFFReadTile(tif, raster, x, y, 0, 0) < 0) {
							// Ignore errors
						}

						for (uint32 line = 0; line < strips; line++, imageDataPtrTileCursor += dstStride)
							memcpy(imageDataPtrTileCursor, raster + (line * tileRowSize), pixelbytes);
					}
				}
			} __finally {
				if (raster != NULL)
					_TIFFfree(raster);
			}

		} else if (planarConfig == PLANARCONFIG_SEPARATE) {
			throw gcnew NotImplementedException("tiled TIFF with separated planes are not supported");
		}
	} __finally {
		hImageData->Free();
	}
}

bool TiffImageCodecPlugin::HasAssociatedAlpha(TIFF *tif)
{
	int extraChannels = 0;

	return (HasAssociatedAlpha(tif, extraChannels));
}

bool TiffImageCodecPlugin::HasAssociatedAlpha(TIFF *tif, int &extraChannels)
{
	uint16 planarConfig;
	uint16 samplesPerPixel = 0;
	uint16 extraSamplePerPixel = 0, *extraSampleType;
	bool hasAlpha = false;

	if (TIFFGetFieldDefaulted(tif, TIFFTAG_PLANARCONFIG, &planarConfig) == 0)
		throw gcnew InvalidOperationException("unknown planar configuration");
	if (TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel) == 0)
		throw gcnew InvalidOperationException("unknown samples per pixel");
	if (TIFFGetField(tif, TIFFTAG_EXTRASAMPLES, &extraSamplePerPixel, &extraSampleType) == 0)
		extraSamplePerPixel = 0;

	switch (planarConfig) {
		case PLANARCONFIG_CONTIG:
			hasAlpha = ((extraSamplePerPixel > 0) && (extraSampleType[0] == EXTRASAMPLE_ASSOCALPHA));
			break;
		case PLANARCONFIG_SEPARATE:
			for (int i = 0; i < extraSamplePerPixel; i++) {
				if (extraSampleType[i] == EXTRASAMPLE_ASSOCALPHA) {
					hasAlpha = true;
					break;
				}
			}
			break;
		default:
			throw gcnew InvalidOperationException("unknown planar configuration value");
	}

	extraChannels = extraSamplePerPixel - ((hasAlpha == true) ? 1 : 0);

	return (hasAlpha);
}

uint16 TiffImageCodecPlugin::GetRealSamplesPerPixel(TIFF *tif)
{
	int extraChannels = 0;

	return (GetRealSamplesPerPixel(tif, extraChannels));
}

uint16 TiffImageCodecPlugin::GetRealSamplesPerPixel(TIFF *tif, int &extraChannels)
{
	uint16 samplesPerPixel = 0;
	uint16 extraSamplePerPixel = 0, *extraSampleType;

	if (TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel) == 0)
		throw gcnew InvalidOperationException("unknown samples per pixel");
	if (TIFFGetField(tif, TIFFTAG_EXTRASAMPLES, &extraSamplePerPixel, &extraSampleType) == 0)
		extraSamplePerPixel = 0;

	if (extraSamplePerPixel > 0) {
		// Extra channel exists
		extraChannels = extraSamplePerPixel;

		// Associated alpha is not considered as extra channel
		if (HasAssociatedAlpha(tif) == true)
			extraSamplePerPixel -= 1;
		
		return (samplesPerPixel - extraSamplePerPixel);
	} else {
		// No extra channel
		extraChannels = 0;

		return (samplesPerPixel);
	}
}

void TiffImageCodecPlugin::PostDivideAlpha(TIFF *tif, Image ^image)
{
	if (HasAssociatedAlpha(tif) == true) {
		GCHandle ^hImageData = GCHandle::Alloc(image->GetPixelData(), GCHandleType::Pinned);

		__try {
			unsigned char *imageDataPtr = reinterpret_cast<unsigned char*>(hImageData->AddrOfPinnedObject().ToPointer());
			unsigned char *imageDataPtrEnd = imageDataPtr + image->Stride * image->Height;

			while (imageDataPtr < imageDataPtrEnd) {
				float r = (float)imageDataPtr[0] / 255.0f;
				float g = (float)imageDataPtr[1] / 255.0f;
				float b = (float)imageDataPtr[2] / 255.0f;
				float a = (float)imageDataPtr[3] / 255.0f;

				if (a > 0.0f) {
					imageDataPtr[3] = (unsigned char)(min<float>(1.0f, (r / a)) * 255.0f);
					imageDataPtr[2] = (unsigned char)(min<float>(1.0f, (g / a)) * 255.0f);
					imageDataPtr[1] = (unsigned char)(min<float>(1.0f, (b / a)) * 255.0f);
				} else {
					imageDataPtr[3] = 0;
					imageDataPtr[2] = 0;
					imageDataPtr[1] = 0;
				}

				imageDataPtr += 4;
			}

		} __finally {
			hImageData->Free();
		}
	}
}

void TiffImageCodecPlugin::OrientateTiff(TIFF *tif, Image ^image)
{
	uint16 orientation;

	if (TIFFGetField(tif, TIFFTAG_ORIENTATION, &orientation) == 0)
		orientation = ORIENTATION_TOPLEFT;

	switch (orientation) {
		case ORIENTATION_TOPLEFT:
			image->FlipVertically();
			break;
		case ORIENTATION_TOPRIGHT:
			image->FlipVertically();
			break;
		case ORIENTATION_BOTRIGHT:
			break;
		case ORIENTATION_BOTLEFT:
			break;

		case ORIENTATION_LEFTTOP:
			break;
		case ORIENTATION_RIGHTTOP:
			break;
		case ORIENTATION_RIGHTBOT:
			break;
		case ORIENTATION_LEFTBOT:
			break;
	}
}

bool TiffImageCodecPlugin::CheckAvailability()
{
	return (true);
}

ImageInfo ^TiffImageCodecPlugin::QueryImageInfo(Stream ^fs, ImageCodec::CodecCriteria ^criteria)
{
	TiffStream stream;
	ImageInfo ^info = gcnew ImageInfo();

	// Prepare TIFF stream handle
	stream.Handle = fs;

	TIFF *tif = TIFFClientOpen(
		"", "r", &stream,
		LibTiffReadProc, LibTiffWriteProc, LibTiffSeekProc, LibTiffCloseProc,
		LibTiffSizeProc, LibTiffMapProc, LibTiffUnmapProc
		);

	// Warning: tif_fd is declared as 'int' currently (see libTIFF), 
	// may result in incorrect file pointers inside libTIFF on 
	// 64bit machines (sizeof(int) != sizeof(long)). 
	// Needs to be fixed within libTIFF.
	if (tif != NULL)
		tif->tif_fd = (long)&stream;

	int width = 0, height = 0;

	TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width);
	TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height);

	info->ContainerFormat = ImageFormat::Tiff;
	info->Width = width;
	info->Height = height;
	info->PixelType = GetPixelTypeFromTiff(tif, criteria);

	return (info);
}

Image ^TiffImageCodecPlugin::Load(Stream ^fs, ImageCodec::CodecCriteria ^criteria)
{
	Image ^image;
	TiffStream stream;

	// Prepare TIFF stream handle
	stream.Handle = fs;

	TIFF *tif = TIFFClientOpen(
		"", "r", &stream,
		LibTiffReadProc, LibTiffWriteProc, LibTiffSeekProc, LibTiffCloseProc,
		LibTiffSizeProc, LibTiffMapProc, LibTiffUnmapProc
		);

	// Warning: tif_fd is declared as 'int' currently (see libTIFF), 
	// may result in incorrect file pointers inside libTIFF on 
	// 64bit machines (sizeof(int) != sizeof(long)). 
	// Needs to be fixed within libTIFF.
	if (tif != NULL)
		tif->tif_fd = (long)&stream;

	Pixel::Type tiffPixel = GetPixelTypeFromTiff(tif, criteria), tiffConvPixel = Pixel::Type::None;
	int width = 0, height = 0;

	TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width);
	TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height);

	// Check for hardware/software support
	if (Pixel::IsSupportedInternalFormat(tiffPixel) == false) {
		if ((criteria->IsDefined(ImageCodec::HardwareSupport) == false) || ((Boolean)criteria[ImageCodec::HardwareSupport] == false)) {
			// Pixel type not directly supported by hardware... try to guess suitable software conversion
			tiffConvPixel = Pixel::GuessBestSupportedConvertion(tiffPixel);
			if (tiffConvPixel == Pixel::Type::None)
				throw gcnew InvalidOperationException("pixel type " + tiffPixel.ToString() + " is not supported by hardware neither software");
		} else
			throw gcnew InvalidOperationException("pixel type " + tiffPixel.ToString() + " is not supported by hardware");
	} else
		tiffConvPixel = tiffPixel;

	// Allocate image
	image = Image::CreateImage(width, height, tiffPixel);

	// Load pixel data
	uint16 sampleFormat = 0;
	uint16 photometric = 0;
	uint16 bitPerSample = 0;
	uint16 samplesPerPixel = 0;
	uint16 bitPerPixel;

	bool useIntegerPixel = (criteria->IsDefined(ImageCodec::IntegerFormatSupport) == true) && ((Boolean)criteria[ImageCodec::IntegerFormatSupport] == true);

	TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric);
	// Determine pixel format (default to SAMPLEFORMAT_UINT)
	if ((TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &sampleFormat) == 0) || (sampleFormat == SAMPLEFORMAT_VOID))
		sampleFormat = SAMPLEFORMAT_UINT;
	
	TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bitPerSample);

	samplesPerPixel = GetRealSamplesPerPixel(tif);
	bitPerPixel = bitPerSample * samplesPerPixel;

	switch (photometric) {
		case PHOTOMETRIC_RGB:
			switch (samplesPerPixel) {
				case 3:
					switch (sampleFormat) {
						case SAMPLEFORMAT_UINT:
							switch (bitPerPixel) {
								case 3:
								case 6:
								case 12:
									LoadGenericRgbaTiff(tif, image);
									break;
								case 8:
								case 15:
								case 16:
								case 24:
								case 48:
									LoadGenericTiff(tif, image);
									break;
								case 96:
									LoadGenericTiff(tif, image);
									break;
								default:
									throw gcnew InvalidOperationException(GetPhotometric(photometric) + " pixel format (UINT, " + GetPhotometric(photometric) + ") with " + bitPerPixel + " is not supported");
									break;
							}
							break;
						case SAMPLEFORMAT_INT:
							throw gcnew InvalidOperationException(GetPhotometric(photometric) + " pixel format (INT, " + GetPhotometric(photometric) + ") with " + bitPerPixel + " is not supported");
							break;
						case SAMPLEFORMAT_IEEEFP:
							switch (bitPerPixel) {
								case 48:
								case 96:
								case 192:
									LoadGenericTiff(tif, image);
									break;
								default:
									throw gcnew InvalidOperationException(GetPhotometric(photometric) + " pixel format (FLOAT, " + GetPhotometric(photometric) + ") with " + bitPerPixel + " bit is not supported");
							}
							break;
						case SAMPLEFORMAT_COMPLEXIEEEFP:
							throw gcnew InvalidOperationException(GetPhotometric(photometric) + " pixel format (FLOAT COMPLEX, " + GetPhotometric(photometric) + ") is not supported");
							break;
						case SAMPLEFORMAT_COMPLEXINT:
							throw gcnew InvalidOperationException(GetPhotometric(photometric) + " pixel format (INT COMPLEX, " + GetPhotometric(photometric) + ") is not supported");
							break;
					}
					break;
				case 4:
					switch (sampleFormat) {
						case SAMPLEFORMAT_UINT:
							switch (bitPerPixel) {
								case 4:
								case 8:
								case 16:
									LoadGenericRgbaTiff(tif, image);
								case 32:
								case 64:
								case 128:
									LoadGenericTiff(tif, image);
									break;
								default:
									throw gcnew InvalidOperationException("RGBA pixel format (UINT, " + GetPhotometric(photometric) + ") with " + bitPerPixel + " is not supported");
									break;
							}
							break;
						case SAMPLEFORMAT_INT:
							throw gcnew InvalidOperationException("RGBA pixel format (INT, " + GetPhotometric(photometric) + ") with " + bitPerPixel + " is not supported");
							break;
						case SAMPLEFORMAT_IEEEFP:
							switch (bitPerPixel) {
								case 64:
								case 128:
									LoadGenericTiff(tif, image);
									break;
								default:
									throw gcnew InvalidOperationException("RGBA pixel format (FLOAT, " + GetPhotometric(photometric) + ") with " + bitPerPixel + " bit is not supported");
							}
							break;
						case SAMPLEFORMAT_COMPLEXIEEEFP:
							throw gcnew InvalidOperationException("RGBA pixel format (FLOAT COMPLEX, " + GetPhotometric(photometric) + ") is not supported");
							break;
						case SAMPLEFORMAT_COMPLEXINT:
							throw gcnew InvalidOperationException("RGBA pixel format (INT COMPLEX, " + GetPhotometric(photometric) + ") is not supported");
							break;
					}
					break;
				default:
					throw gcnew InvalidOperationException("RGB(A) pixel format with " + samplesPerPixel + " samples is not supported");
			}
			break;
		case PHOTOMETRIC_YCBCR:
		case PHOTOMETRIC_CIELAB:
		case PHOTOMETRIC_ICCLAB:
		case PHOTOMETRIC_ITULAB:
			LoadGenericRgbaTiff(tif, image);
			break;
		case PHOTOMETRIC_LOGL:
		case PHOTOMETRIC_LOGLUV:
			LoadLogarithmicTiff(tif, image);
			break;
		case PHOTOMETRIC_SEPARATED:
			LoadGenericTiff(tif, image);
			break;
		case PHOTOMETRIC_MINISWHITE:
		case PHOTOMETRIC_MINISBLACK:
			switch (samplesPerPixel) {
				case 1:
					switch (sampleFormat) {
						case SAMPLEFORMAT_UINT:
							switch (bitPerPixel) {
								case 1:
								case 2:
								case 4:
									LoadGenericRgbaTiff(tif, image);
									break;
								case 8:
								case 16:
									LoadGenericTiff(tif, image);
									if (photometric == PHOTOMETRIC_MINISWHITE)
										image->Negative();
									break;
								default:
									throw gcnew InvalidOperationException("PHOTOMETRIC_MINISBLACK pixel format with " + bitPerPixel +" bits per sample is not supported");
							}
							break;
						case SAMPLEFORMAT_INT:
							throw gcnew InvalidOperationException("PHOTOMETRIC_MINISBLACK pixel format (INT) is not supported");
						case SAMPLEFORMAT_IEEEFP:
							throw gcnew InvalidOperationException("PHOTOMETRIC_MINISBLACK pixel format (FLOAT) is not supported");
						case SAMPLEFORMAT_COMPLEXIEEEFP:
							throw gcnew InvalidOperationException("PHOTOMETRIC_MINISBLACK pixel format (FLOAT COMPLEX) is not supported");
						case SAMPLEFORMAT_COMPLEXINT:
							throw gcnew InvalidOperationException("PHOTOMETRIC_MINISBLACK pixel format (INT COMPLEX) is not supported");
						default:
							throw gcnew InvalidOperationException("PHOTOMETRIC_MINISBLACK pixel format with UNKNOWN sample format is not supported");
					}
					break;
				default:
					throw gcnew InvalidOperationException("PHOTOMETRIC_MINISBLACK pixel format with " + samplesPerPixel + " samples is not supported");
			}
			break;
		case PHOTOMETRIC_PALETTE:
			LoadGenericRgbaTiff(tif, image);
			break;
		default:
			throw gcnew InvalidOperationException(GetPhotometric(photometric) + " pixel format is not supported");
	}

	// Convert image to supported format, if necessary
	if ((tiffConvPixel != Pixel::Type::None) && (tiffConvPixel != tiffPixel))
		image = image->Convert(tiffConvPixel);

	return (image);
}

void TiffImageCodecPlugin::Save(Stream ^fs, Image ^image, ImageFormat format, ImageCodec::CodecCriteria ^criteria)
{
	throw gcnew NotImplementedException();
}
